use crate::utils::config::MongodbConfig;
use crate::{AppResult, Pagination, PaginationQuery, ToPagination};
use async_trait::async_trait;
use futures::StreamExt;
use std::borrow::Borrow;
use std::sync::Arc;
use wither::bson::oid::ObjectId;
use wither::bson::{doc, Document};
use wither::mongodb::options::{self, ClientOptions, Credential, Tls};
pub use wither::mongodb::{Client, Database};
use wither::Model;

pub type MDB = Arc<Database>;
pub type MClient = Arc<Client>;
pub fn try_new_from_config(config: &MongodbConfig) -> AppResult<(MDB, MClient)> {
    let builder = ClientOptions::builder()
        .hosts(
            config
                .connections
                .iter()
                .map(|v| v.into())
                .collect::<Vec<_>>(),
        )
        .credential(Some(
            Credential::builder()
                .username(Some(config.username.clone()))
                .password(Some(config.password.clone()))
                .build(),
        ))
        .repl_set_name(config.repl_set_name.clone())
        .tls(config.tls.clone().map(|v| Tls::Enabled(v.into())));

    let client = Client::with_options(builder.build())?;
    let db = client.database(config.db.as_str());
    Ok((Arc::new(db), Arc::new(client)))
}

#[async_trait]
pub trait ModelEx: Model + Clone {
    fn id_key() -> String {
        "_id".into()
    }
    async fn find_by_id<I, O>(db: &Database, id: I, option: O) -> AppResult<Option<Self>>
    where
        I: Into<ObjectId> + Send,
        O: Into<Option<options::FindOneOptions>> + Send,
    {
        let id_key = Self::id_key();
        Ok(Self::find_one(db, doc! {id_key:id.into()}, option).await?)
    }
    /// ## find_and_pagination
    /// 查找并且分页
    /// 如果size小于0则全部查询
    async fn find_and_pagination<F, Q>(
        db: Arc<Database>,
        filter: F,
        query: Q,
    ) -> AppResult<Pagination<Self>>
    where
        F: Into<Option<Document>> + Clone + Send,
        Q: Borrow<PaginationQuery> + Send,
    {
        let query: &PaginationQuery = query.borrow();
        if query.size < 0 {
            let mut cursor = Self::find(&*db, filter, None).await?;
            let mut arr_res = vec![];
            while let Some(item) = cursor.next().await {
                arr_res.push(item?);
            }
            return Ok(arr_res.new_pagination_with_total_count(query, false, false, arr_res.len()));
        }
        let (f1, f2) = (filter.clone(), filter.clone());
        let count = Self::collection(&*db.clone())
            .count_documents(f1, None)
            .await?;
        let mut cursor = Self::find(&*db.clone(), f2, query.clone()).await?;
        let mut arr_res = Vec::new();
        while let Some(item) = cursor.next().await {
            arr_res.push(item?);
        }
        let has_next_page = query.size as usize * (query.page + 1) < count as usize;
        Ok(arr_res.new_pagination_with_total_count(
            query,
            has_next_page,
            query.page > 0,
            count as usize,
        ))
    }
}
impl<T> ModelEx for T where T: Model + Clone {}
